La Regenta
Análisis de la novela en R

Introducción

En este apartado, se analiza la obra “La Regenta” de Leopoldo Alas Clarín a partir de diferentes técnicas de análisis de contenido. Se tratan, en su mayor parte, de acercamientos estadísticos o cuantitativos a los textos literarios. Por lo tanto, representan una visión limitada de la obra y solo complementaria a la lectura y el análisis cualitativo detenido de la misma.

Nos acercaremos al texto, primero, intentando descubrir cuáles palabras se destacan, sus frecuencias y distribuciones. Luego, analizaremos la obra desde la perspectiva de los personajes, sus relaciones y sus apariciones en la obra.

Las palabras que cuentan

El primer paso del análisis consiste en cargar los datos que hemos preparado en la sección de preparación de los datos y convertirlo en un objeto de tipo corpus. Para ello, emplearemos el paquete de R llamado quanteda, que resulta bastante completo para el análisis de contenido.

El código abajo carga los datos en la memoria, los convierte en un corpus y muestra algunas estadísticas básicas de los capítulos de la obra, como el número de palabras (tokens), el número de palabras únicas (types) y la cantidad de frases.

Code
# carga los datos de "La Regenta"
load("../textos/Regenta.RData")

# carga el paquete quanteda para 
# algunos análisis
library(quanteda)

# Elimina el título y el prólogo
regc <- regc[3:nrow(regc),]

# convierte en un documento corpus
cp <- corpus(regc, text_field = "texto")

# define los capítulos como nombre
# de los documentos
docnames(cp) <- regc$capitulo

# muestra estadísticas básicas de
# cada capítulo
reactable::reactable(summary(cp))

Vemos que los treinta capítulos de la obra tienen una extensión que va de poco más de ocho mil hasta aproximadamente veintiún mil palabras. No resulta nada sorprendente que las palabras únicas y el número de frases varían de forma proporcional.

Más interesante resulta explorar cuáles palabras se destacan en la obra. Para ello, emplearemos una técnica llamada análisis de frecuencia de palabras. En este caso, emplearemos una matriz de términos de documentos (document-feature matrix), que es una representación matricial de los textos en la que las filas representan los documentos y las columnas las palabras. Cada celda de la matriz contiene el número de veces que una palabra aparece en un documento.

Code
# convierte en una matriz de términos
dtm <- dfm( tokens(cp, remove_punct = TRUE, 
                  remove_numbers = TRUE, 
                  remove_symbols = TRUE), 
           tolower = TRUE)

dtm <- dfm_remove(dtm, c(stopwords("es"),"si"))

# muestra las 50 palabras más frecuentes
topfeatures(dtm, 50)
      don       ana     usted magistral   aquella     aquel    álvaro    víctor 
     1789       894       822       767       758       731       488       487 
     casa   regenta     señor   vetusta      allí       ser   después     decía 
      477       467       458       455       446       430       415       414 
     doña       vez     mesía     podía     ahora      bien      ojos    fermín 
      411       390       372       368       361       349       346       345 
      así       iba       dos    quería      dijo   siempre     menos     mismo 
      344       333       330       328       325       325       322       318 
   hombre       tan      vida      dios     veces quintanar     mundo     mujer 
      314       312       311       309       305       303       285       284 
    todas     sabía       ver    tiempo      alma       día   parecía      amor 
      279       279       279       278       275       271       267       267 
  aquello     decir 
      265       257 

En este caso, hemos considerado palabras aisladas, pero podemos repetir el análisis con bigramas o trigramas, que son secuencias de dos o tres palabras consecutivas. Estas combinaciones permiten encontrar secuencias de ideas que pueden revelar ciertos contenidos o patrones en el texto.

Code
tk <- tokens(cp, remove_punct = TRUE, 
                  remove_numbers = TRUE, 
                  remove_symbols = TRUE)

tk <- tokens_remove(tk, stopwords("es"))

bi <- tokens_ngrams(tk, n = 2)

# convierte en una matriz de términos
dtm <- dfm(bi)

# muestra los 50 bigramas más frecuentes
topfeatures(dtm, 50)
     don_víctor      don_álvaro      don_fermín     don_pompeyo      doña_paula 
            450             390             302             123             119 
        tal_vez      don_santos  doña_petronila      don_carlos   aquella_noche 
            105              93              66              60              51 
  aquel_momento    don_custodio        doña_ana    doña_anuncia        cada_vez 
             48              47              47              47              46 
   don_cayetano   aquella_tarde      don_frutos     doña_camila señor_magistral 
             46              45              43              38              37 
   aquel_hombre   don_saturnino        dos_tres  don_robustiano     don_saturno 
             36              35              34              34              32 
    pocas_veces    todas_partes    muchas_veces     usted_señor        cada_día 
             32              31              31              31              30 
  aquella_mujer        si_usted       aquel_día       podía_ser        don_juan 
             30              30              29              29              29 
       voz_baja       debía_ser     doña_rufina       buen_mozo      ana_ozores 
             28              28              28              27              26 
  día_siguiente    álvaro_mesía  caserón_ozores      aire_libre      mire_usted 
             26              26              26              25              25 
   santa_teresa    mundo_entero  cualquier_cosa     doña_águeda       señor_don 
             25              24              24              24              23 
Code
tri <- tokens_ngrams(tk, n = 3)

# convierte en una matriz de términos
dtm <- dfm(tri)

# muestra los 50 trigramas más frecuentes
topfeatures(dtm, 50)
         don_álvaro_mesía      don_pompeyo_guimarán      don_víctor_quintanar 
                       22                        20                        17 
   don_saturnino_bermúdez       don_santos_barinaga        don_frutos_redondo 
                       16                        13                        11 
      casa_doña_petronila            dos_tres_veces          dio_media_vuelta 
                       11                         9                         9 
          doña_ana_ozores             si_don_víctor                  ta_ta_ta 
                        8                         8                         8 
          casa_don_víctor         darse_cuenta_ello             don_álvaro_si 
                        8                         8                         8 
 doña_petronila_rianzares            don_fermín_pas      jefe_partido_liberal 
                        8                         7                         7 
partido_liberal_dinástico          don_tomás_crespo        almunia_don_godino 
                        7                         7                         7 
       hermano_mayor_alma            _la_cruz_roja_     don_robustiano_somoza 
                        7                         7                         7 
                 ja_ja_ja        media_hora_después            fray_luis_león 
                        7                         7                         6 
         don_juan_tenorio          decía_don_álvaro            dio_paso_atrás 
                        6                         6                         6 
           ana_don_álvaro           dijo_don_víctor             si_don_álvaro 
                        6                         6                         6 
 don_custodio_beneficiado          señor_don_fermín          gritó_don_víctor 
                        5                         5                         5 
        daba_media_vuelta    hago_cuestión_personal          aquel_don_álvaro 
                        5                         5                         5 
         usted_señor_foja             dijo_voz_baja            don_víctor_vio 
                        5                         5                         5 
         pobre_don_santos        treinta_cinco_años  beneficiado_don_custodio 
                        5                         4                         4 
        hablaban_voz_baja   visitación_olías_cuervo     don_restituto_mourelo 
                        4                         4                         4 
         primera_vez_vida          señor_don_víctor 
                        4                         4 

¿Qué vemos en los resultados de los bigramas y trigramas? El primer patrón que emerge es la presencia de nombres de personajes, como “don Víctor Quintanar”, “don Álvaro” o “do Fermín”. También aparecen nombres de lugares, como “Vetusta” o “Santa Cruz”. Por último, encontramos algunas secuencias que parecen describir acciones o situaciones, como “señor marqués” o “señor don Fermín”.

Diccionarios y codificación temática

diccionario

Code
dic <- dictionary(
  list(
    familia=c("padre", "madre", "hijo", "hija", 
              "hermano", "hermana"),
    sociedad=c("vetustense","puebl","ciudad","conversa",
               "amig","noble","arist"),
    iglesia=c("cura","obispo","sacerdo","confesión",
              "religi","canónig","capilla"),
    economia=c("dinero","deuda","negoci","trabaj",
               "fortuna","rico","rica"),
    sentimientos=c("culpa","alegr","amor","remordimiento",
                   "ridículo","vergüenza","triste"),
    misticismo=c("Fermín de Pas","Fermín","\\bde Pas\\b",
                 "magistral", "espirit","dios"),
    adulterio=c("Álvaro Mesía","Álvaro","Mesía",
                "Presidente del Casino", "marido", 
                "placer", "pasión", "amante","amorío"),
    espacio=c("casino","catedral","vivero","casa",
              "espolón","teatro"),
    tiempo=c("otoño","verano","invierno","primavera",
             "semana santa","navidad")
  )
)
    
library(tenet)
    
tagCorpus(cp, 
          dic, 
          reshape.to = "sentence", 
          defaultPageSize = 3)
Code
dicf <- dictionary(
  list(
    familia=c("padre", "madre", "hijo", "hija", "hermano", "hermana"),
    sociedad=c("vetustense","puebl","ciudad","conversa",
               "amig","noble","arist")
))


plotLexDiv(cp, 
           dicf, 
           title ="La Regenta", 
           subtitle = "Familia y sociedad en la novela", 
           palette = pal$cat.awtools.spalette.6[1:2])

Repetimos el mismo gráfico, ahora con las categorías de tiempo y espacio:

Code
dicf <- dictionary(
  list(
    espacio=c("casino","catedral","vivero",
              "casa","espolón","teatro"),
    tiempo=c("otoño","verano","invierno",
             "primavera","semana santa","navidad")
))

plotLexDiv(cp, 
           dicf, 
           title ='La Regenta', 
           subtitle = "Espacio y tiempo en la novela", 
           palette = pal$cat.brewer.Dark2.8[c(1:4)])

Code
dicf <- dictionary(
  list(
    social=c("casino","teatro","espolón",
             "Paseo de Verano","Paseo de los curas",
             "calle","plaza"),
    religioso=c("catedral","capilla","sacristía",
                "claustro","seminario"),
    doméstico=c("vivero","\\bcasa\\b","hogar","alcoba",
                "habitación","cama")))

ft <- filterWords(cp, dicf)

ft$name[ft$name!="002 - Prólogo"] <- paste0("Capítulo ",ft$name[ft$name!="002 - Prólogo"])

plotSpike(ft, 
          title = 'Los espacios de "La Regenta"', 
          subtitle = "Gráfico de dispersión léxica para distintos tipos de espacio.", 
          palette = pal$cat.brewer.Dark2.8[c(1:4)],
          label.size = 3, 
          line.width = 0.3, 
          ring.col="black")
Code
xy <- countKeywords(cp, 
                    dic, 
                    rel.freq = F, 
                    quietly = TRUE)

# Elimina los términos no encontrados
xy <- xy[xy$frequency>0,]

xy$groups <- NULL

forceDirectedTree(xy,value_col = "frequency",
                  palette = pal$cat.awtools.bpalette.16, 
                  max.radius = 50, 
                  height = 500)

lo mismo se puede ver así:

Code
plotVoronoiTree(data = xy,
                value_col = "frequency")

dd

Juan Benito Argüelles - Nomina de personajes de “La Regenta”

https://cvc.cervantes.es/literatura/cuadernos_del_norte/pdf/23/23_10.pdf

Code
dich <- dictionary(
  list(
    hombres=
      list(
           Alvaro=c("Álvaro Mesía","Álvaro","Mesía",
                    "presidente del casino"),
           Saturnino=c("Saturnino Bermúdez","Saturnino",
                       "Bermúdez","Saturno","Saturnillo"),
           Fermin=c("Magistral","Provisor","Fermín",
                    "De Pas","Fermín de Pas","Magistral"),
           Victor=c("Víctor","Víctor Quintanar","Quintanar"),
           Otros=c(
                   "Agustinito","Amadeo","Anacleto",
                   "Anselmo","Antero","Antón Raíces",
                   "Antonio","Barcaza","Basilio",
                   "Bautista","Bedoya","Belisario",
                    "Benítez","Bismark","Campillo",
                   "\\b[C]hato\\b","don Carlos",
                   "Peláez","Cayetano",
                   "Cayetano Ripamilán","Ripamilán",
                   "Celedonio","Colás","marqués de Corujedo",
                   "Frígilis","Crespo","Custodio",
                   "Olías de Cuervo","señor Cuervo",
                   "Diego","Escosura","\\b[E]studiante\\b",
                   "Pepe","Trabuco","Foja","Fortunato",
                   "Francisco de Asís","Francisco de Pas",
                   "Francisco de Osuna","Francisco Páez", 
                   "señor Páez", "señor de Páez",
                   "Francisco Carraspique","Froilán",
                   "don Frutos","Frutos Redondo","Fulgosio",
                   "Germán","Glocester","Restituto",
                   "señor Infanzón", "el Infanzón",
                   "Iriarte","Joaquinito","Juanito",
                   "Leando","Maroto","marqués de Vegallana",
                   "\\s{1}[M]arqués\\b","Martínez",
                   "Matías","Matiella","monaguillo",
                   "señor Orgaz","\\b[P]alma\\b",
                   "Paco","Paquito","Palomo",
                   "Rodríguez","Parcerisa","Pedro",
                   "\\b[P]erales\\b","Pinón",
                   "Pompeyo","Pompeyo Guimarán","Guimarán",
                   "Robustiano","señor Roque",
                   "Rosendo","don Santos","Sousa",
                   "Trifón","\\b[V]inagre\\b",
                   "Vinculete")
           ),
    mujeres=
      list(
        Ana=c("Ana","Anita","Regenta"),
        Obdulia=c("Obdulia","Obdulia Fandiño",
                  "Fandiño","Obdulita"),
        Otras=c(
                "Agapita","Águeda","Angelina",
                "Anuncita","doña Anuncia","Camila",
                "Carolina","Señora de Infanzón", "la Infanzón",
                "Celestina","Edelmira","Emma",
                "Fabiolita","Fulgencia","Gertrudis",
                "la González","Juana","Lola",
                "doña Lucía","marquesa","Rufina",
                "Olvido","doña Paula","Pepa",
                "Guimarán, Perpétua","\\b[P]ilar\\b",
                "Petra","doña Petronila","Ramona",
                "\\bRita\\b","\\bRosa\\b","Rosita",
                "Rudesinda","Servanda","Társila",
                "\\b[T]eresa\\b","Teresina",
                "Úrsula","Visitación","\\b[V]isita\\b",
                "viuda del marqués de Corujedo")
    )
  )
)




xp <- countKeywords(cp, 
                    dich, 
                    rel.freq = F, 
                    group.var = "capitulo",
                    quietly = TRUE)

# Agrega los resultados por los dos niveles
# de código del diccionario
xx <- aggregate(list(frequency=xp$frequency), 
                by=list(groups=xp$groups, 
                        level1=xp$level2), 
                sum, na.rm=T)

# Elimina los términos no encontrados
# en el corpus
xx <- xx[xx$frequency>0,]

# Ordena por capítulo
xx <- xx[order(xx$groups),]

plotSankey(xx, 
           from = "level1", 
           to="groups", 
           value = "frequency", 
           opacity = 0.05)
Code
plotStream(xx, 
           x="groups",
           y="frequency", 
           group="level1",
           palette=pal$cat.ggsci.simpsons.16)

dddd

Code
# Reorganiza el corpus según
# sentencias o frases
cs <- corpus_reshape(cp, "sentences")

# Calcula la frecuencia en la
# que dos codigos del mismo 
# diccionario aparecen juntos
# en cada frase
d1 <- matchCodes(cs, 
                dic, 
                level = 1, 
                quietly=TRUE)

# Ordena los resultados de mayor a menor
d1 <- d1[order(d1$value, decreasing = T),]


plotChord(d1, 
          from = "term1", 
          to ="term2", 
          value= "value")

Ahora con los personajes

Code
# Reorganiza el corpus según
# sentencias o frases
cs <- corpus_reshape(cp, "sentences")

# Calcula la frecuencia en la
# que dos codigos del mismo 
# diccionario aparecen juntos
# en cada frase
d1 <- matchCodes(cs, 
                dich, 
                level = 2, 
                quietly=TRUE)

# Ordena los resultados de mayor a menor
d1 <- d1[order(d1$value, decreasing = T),]


plotChord(d1, 
          from = "term1", 
          to ="term2", 
          value= "value", elementId = "chord2")

aaa

Code
nn <- data.frame()

for(i in 1:30){
  
  c1 <- cp[i]
  
  c1 <- corpus_reshape(c1, to="paragraphs")
  
  d1 <- matchCodes(c1, 
                   dic.per, 
                   level = 2, 
                   quietly=TRUE)
  
  d1$capitulo <- i
  
  d1 <- d1[order(d1$value, decreasing = T),]
  
  nn<- rbind(nn, d1)
  
}



library(network)
library(sna)
library(ggnetwork)

px <- list()

for(i in 1:30){

  n1 <- network(nn[nn$capitulo==i,], 
                directed = F)
  
  gn <- ggnetwork(n1, 
                  layout = "kamadakawai", 
                  cell.jitter = 0.75)

  p <- ggplot(gn, 
              aes(x = x, 
                  y = y, 
                  xend = xend, 
                  yend = yend)) +
          geom_edges(
                color = "grey90",
                aes(size=value), 
                curvature=0.25) +
        geom_edges(
                size=0.1, 
                curvature=0.25, 
                color="purple") +
          geom_nodetext(
                aes(label = vertex.names),
                fontface = "bold")+
          theme_blank()+
          labs(title=paste0("**Capítulo ",i,"**"))+
          theme(plot.title = 
                  ggtext::element_markdown(
                          size=15, 
                          color="darkgreen"), 
                legend.position = "none",
                plot.margin=grid::unit(
                  c(0.75,0.75,0.75,0.75), 
                  "cm"))

  px[[i]] <- p

}

library(egg)

ggarrange(plots=px, 
          ncol=3, 
          nrow=10)